package org.gnomus.server;

import org.gnomus.util.*;

import javax.servlet.RequestDispatcher;
import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.Set;

import static org.gnomus.util.strings.simplify;

public class Dispatcher {

    private static final Set<Class> controllers = classes.getAllClasses(Controller.class);
    private static final Map<String, Map<String,Method>> c2m = methods.collectMethods(controllers);

    public static boolean dispatch() {
        Context ctx = Context.get();
        String uri = ctx.uri;
        while (uri.length() > 0 && uri.charAt(0) == '/') {
            uri = uri.substring(1);
        }
        while (uri.length() > 0 && uri.charAt(uri.length() - 1) == '/') {
            uri = uri.substring(0, uri.length() - 1);
        }
        if (strings.isEmpty(uri)) {
            if ("utivyti.appspot.com".equals(ctx.req.getServerName())) {
                uri = "utivyti/landing";
            } else {
                uri = "home/landing";
            }
        }
        int ix = uri.indexOf('/');
        if (ix < 0 || ix + 1 == uri.length()) {
            uri = uri + "/landing";
        }
        String path = "WEB-INF/jsp/" + uri + ".jsp";
        if (invokeMethod(uri)) {
            if (io.exists(new File(path))) {
                RequestDispatcher rd = ctx.req.getRequestDispatcher(path);
                try {
                    rd.include(ctx.req, ctx.res);
                    return true;
                } catch (Throwable t) {
                    throw new Error(t);
                }
            }
            return true;
        }
        return false;
    }

    private static boolean invokeMethod(String uri) {
        int ix = uri.indexOf('/');
        String cn = uri.substring(0, ix); //controller name
        String mn = uri.substring(ix + 1); // action name
        Map<String, Method> a2m = c2m.get(simplify(cn));
        if (a2m != null) {
            Method m = a2m.get(simplify(mn));
            if (m != null) {
                return invokeMethod(m);
            }
        }
        return false;
    }

    private static boolean invokeMethod(Method m) {
        Context ctx = Context.get();
        Object ci = classes.newInstance(m.getDeclaringClass());
        Map<String, String> params = ctx.params;
        Map<String, Field> fields = classes.getFields(ci);
        for (Map.Entry<String, Field> e: fields.entrySet()) {
            String name = e.getKey().toLowerCase();
            String value = params.get(name);
            if (value != null) {
                Field f = e.getValue();
                if (f.getType().equals(String.class)) {
                    classes.setField(ci, f, value);
                } else if (f.getType().equals(Integer.class)) {
                    classes.setField(ci, f, Integer.parseInt(value));
                }
            }
        }
        try {
            Object o = m.invoke(ci);
            return o instanceof Boolean ? (Boolean)o : false;
        } catch (Throwable t) {
            throw new Error(t);
        }
    }
}
